package strategies;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import plots.Plot;
import agents.UAP;
import base.Environment;


/**
 * A harvesting Strategy.
 * <p>
 * Encases a path in the environment graph with associated target harvesting units for every plot.
 */
public class Strategy implements Cloneable {
	
	private static int nextId = 0;
	
	protected int id;
	
	/**
	 * The sum of the harvesting targets.
	 */
	int totalTarget;
	
	/**
	 * The actual plot path with associated targets.
	 */
	LinkedList<PlotTarget> path;
	
	HashMap<Plot, Integer> distinctPlots;
	
	
	public Strategy(LinkedList<PlotTarget> path) {
		this.id = nextId++;
		this.path = pathClone(path);
		updateTotalTarget();
	}	
	
	/**
	 * Generates and aggregates distinct plots used in this strategy that are farmed not just visited;
	 * Stores in distinctPlots;
	 */
	private final void generateDistinct(){
		distinctPlots = new HashMap<Plot, Integer>();
		
		for (PlotTarget plotTarget : path)
			if (distinctPlots.containsKey(plotTarget.plot) && plotTarget.target>0)
				distinctPlots.put(plotTarget.plot, distinctPlots.get(plotTarget.plot) + plotTarget.target);
			else
				distinctPlots.put(plotTarget.plot, plotTarget.target);				
	}
	
	/**
	 * Makes a deep copy of the given path.
	 */
	static LinkedList<PlotTarget> pathClone(LinkedList<PlotTarget> path) {
		LinkedList<PlotTarget> clone = new LinkedList<PlotTarget>();
		for (PlotTarget plotTarget : path)
			clone.add(plotTarget.clone());
		return clone;
	}
	
	final void updateTotalTarget() {
		this.totalTarget = 0;
		for (PlotTarget plotTarget : path) {
			this.totalTarget += plotTarget.target;
		}
		generateDistinct();
	}
	
	/**
	 * Clears the utility records.
	 */
	public void clearUtilities() {
		for (PlotTarget plotTarget : path) {
			plotTarget.averageUtility.clear();
		}
		updateTotalTarget();
	}
	
	public Set<Plot> getDistinctPlots(){
		return distinctPlots.keySet();
	}
	
	
	/**
	 * Harvests resources from the environment.
	 * <p>
	 * The harvesting ratios will be normalized according to the actual quota. - so they actually are percentage
	 * 	 TODO: autoimprove strategy after farm -- removed in this iteration
	 * 			rethink how to improve utility after each succesful farm - not by ratio but by actual quantity farmed ? - might be same thing tho
	 * 
	 * @param quota the actual harvest quota
	 * @param env the environment
	 * 
	 * @return the resulting UAP aggregate
	 */
	public UAP harvest(double quota, Environment env) {
		double utility = 0D, amount = 0D;
		PlotTarget previous = null;
		double accumulatedPenalty = 0D;
		
		for (PlotTarget plotTarget : path) {
			if (previous != null) {
				accumulatedPenalty += env.getDistanceBetween(previous.plot, plotTarget.plot);
			}
			
			// the penalty accumulated for crossing plots that aren't farmed should be incurred on
			// the first encountered target plot
			if (plotTarget.target == 0) {
				continue;
			}
			
			double actual = plotTarget.target * quota / totalTarget;
			double plotUtility;
			
			if (plotTarget.plot.getYield() >= actual) {
				plotUtility = actual;
				plotTarget.plot.setYield(plotTarget.plot.getYield() - actual);
			} else {
				plotUtility = plotTarget.plot.getYield();
				plotTarget.plot.setYield(0D);
			}
			amount += plotUtility;
			plotUtility -= accumulatedPenalty;
			accumulatedPenalty = 0D;
			
			utility += plotUtility;
			plotTarget.averageUtility.increaseUtility(plotUtility);
			previous = plotTarget;
			
			plotTarget.plot.averageUtility.increaseUtility(plotUtility);
		}
		
		// must dump accumulated penalty on last plot
		if (path.getLast().target == 0) {
			utility -= accumulatedPenalty;
			path.getLast().averageUtility.increaseUtility(-accumulatedPenalty);
			path.getLast().plot.averageUtility.increaseUtility(-accumulatedPenalty);
		}
		
		return new UAP(utility, amount, amount/quota);
	}
	
	@Override
	public boolean equals(Object o) {		
		
		if (!(o instanceof Strategy))
			return false;
		
		Strategy strategy = (Strategy) o;
		
		if (this.path.size() != strategy.path.size())
			return false;		
		
		if (this.totalTarget != strategy.totalTarget)
			return false;
		
		Iterator<PlotTarget> itThat = strategy.path.descendingIterator();
		Iterator<PlotTarget> itThis = this.path.descendingIterator();
		
		for(PlotTarget x = itThis.next(),y = itThat.next();
				itThis.hasNext() && itThat.hasNext();
				x = itThis.next(), y = itThat.next())
			if (!x.equals(y))
				return false;
		
		return true;
	}
	
	@Override
	public String toString() {
		return new StringBuilder(this.getClass().getSimpleName()).append(" #").append(id)
				.append(':').append(path.toString()).toString();
	}
	
	/**
	 * Custom strategy implementations must override this.
	 */
	@Override
	public Strategy clone() {
		return new Strategy(path);
	}
	
	public List<PlotTarget> getSortedPlotTargets() {
		ArrayList<PlotTarget> sorted = new ArrayList<PlotTarget>(path);
		Collections.sort(sorted);
		return sorted;
	}
	
	public LinkedList<PlotTarget> getPath(){
		return path;
	}
}
